﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace XDL.Framework.Messaging
{
    public class EventDispatcher
    {
        class EventClientProxy
        {
            struct Event
            {
                public int ID;
                public EventArgs Args;
                public Event(int id, EventArgs args)
                {
                    ID = id;
                    Args = args;
                }
            }
            
            public IEventDispatcherClient Client
            {
                get;
                private set;
            }
            List<Event> m_pendingEvents = new List<Event>();

            public EventClientProxy(IEventDispatcherClient c)
            {
                this.Client = c;
            }
            public void PostEvent(int eventType, EventArgs e)
            {
                m_pendingEvents.Add(new Event(eventType,e));
            }

            public void Update(GameTime gameTime)
            {
                int count = m_pendingEvents.Count;
                for (int i = count - 1; i >= 0; i--)
                {
                    Event e = m_pendingEvents[i];
                    Client.ProcessEvent(gameTime, e.ID, e.Args);
                    // no need to keep events (they must be either processed now or never!)
                    m_pendingEvents.RemoveAt(i);
                }
            }
        }
        List<EventClientProxy> m_clients = new List<EventClientProxy>(256);
        List<int> m_clientsToBeRemoved = new List<int>(32);
        List<IEventDispatcherClient> m_clientsToBeAdded = new List<IEventDispatcherClient>(32);

        public EventDispatcher() 
        {
        }

        public void Update(GameTime gameTime)
        {
            int count = m_clients.Count;
            foreach (EventClientProxy it in m_clients)
                it.Update(gameTime);

            // indicates the list has been modified during iteration (not allowed!)
            System.Diagnostics.Debug.Assert(count == m_clients.Count);
            
            RemoveOldClients();
            RegisterClients();
        }

        void RemoveOldClients()
        {
            foreach (int id in m_clientsToBeRemoved)
            {
                for (int i = 0; i < m_clients.Count; i++)
                {
                    if (m_clients[i].Client.ID == id)
                    {
                        m_clients.RemoveAt(i);
                        break;
                    }
                }
            }
            m_clientsToBeRemoved.Clear();
        }

        void RegisterClients()
        {
            foreach (IEventDispatcherClient it in m_clientsToBeAdded)
                m_clients.Add(new EventClientProxy(it));
            m_clientsToBeAdded.Clear();
        }

        /// <summary>
        /// Queue an event.
        /// </summary>
        /// <param name="clientId">Target object</param>
        /// <param name="eventType">Event identifier</param>
        /// <param name="e">Event arguments</param>
        public void PostEvent(int clientId, int eventType, EventArgs e)
        {
            EventClientProxy pxy = m_clients.Find(m => m.Client.ID == clientId);
            if ( pxy != null )
                pxy.PostEvent(eventType, e);
        }

        public void RegisterClient(IEventDispatcherClient client)
        {
            System.Diagnostics.Debug.Assert(m_clientsToBeAdded.Find(m => m.ID == client.ID) == null);
            m_clientsToBeAdded.Add(client);
        }

        public void UnregisterClient(IEventDispatcherClient client)
        {
            m_clientsToBeRemoved.Add(client.ID);
        }

        public int ActiveClients
        {
            get
            {
                return m_clients.Count;
            }
        }

        public void Clear()
        {
            m_clients.Clear();
            m_clientsToBeRemoved.Clear();
            m_clientsToBeAdded.Clear();
        }
    }
}
